package mya_dc.master_server.database;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Set;
import java.util.Map.Entry;

import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

import mya_dc.master_server.MasterServer;
import mya_dc.shared_classes.MYA_Authentication;
import mya_dc.shared_classes.UserProject;

/**
 * The clients' DB
 * 
 * @author      Adam Levi
 * <br>			MYA
 */
public class ClientsHashTable implements TableModel, Serializable
{
	private static final long serialVersionUID = 1L;
	private Hashtable<UserProject, Long> m_MYA_UserProjToServerID = new Hashtable<UserProject, Long>();
	private Hashtable<String, String>			m_UserToPassword = new Hashtable<String, String>();

	/**
	 * clears the hash-table
	 */
	public synchronized void clear()
	{
		m_MYA_UserProjToServerID.clear();
		m_UserToPassword.clear();
	}
	
	/**
	 * gets the server mapped to a given MYA_Authentication
	 * 
	 * @param authentication - a MYA_Authentication object
	 * 
	 * @return 	The server ID if it MYA_Authentication is mapped to the server
	 * 			null otherwise
	 */
	public synchronized Long getServer(MYA_Authentication authentication)
	{
		UserProject userProj = new UserProject(
				authentication.getUserName(),
				authentication.getProject());
		return m_MYA_UserProjToServerID.get(userProj);
	}
	
	/**
	 * 
	 * @param username - a user name.
	 * 
	 * @return 	true if the user-name exists in the hash-table, false otherwise
	 */
	public synchronized boolean userExists(String username)
	{
		return m_UserToPassword.containsKey(username);
	}
	
	
	/**
	 * checks if the given mapping exists in the hash-table
	 * 
	 * @param info - User and a project
	 * @param ServerID - The server's ID
	 * 
	 * @return 	true if the given mapping exists, otherwise false.
	 */
	public synchronized boolean mappingExists(
			UserProject info, 
			long ServerID,
			ServersHashTable compServers)
	{
		Long ID = m_MYA_UserProjToServerID.get(info);
		if (ID == null)
		{
			//in this case we try to add the mapping of the userProject
			//to the server since the server contains this project
			//we can only add the mapping if the password exists
			//in the system
			
			if (m_UserToPassword.containsKey(info.m_sUserName))
			{
				m_MYA_UserProjToServerID.put(info, ServerID);
				compServers.registerClient(ServerID);
				return true;
			}
			return false;
		}
		else
			return (ID == ServerID);
	}
	/**
	 * checks if the given password is the password that exists in the DB
	 * 
	 * @param username - a user name.
	 * @param password - a password
	 * 
	 * @return 	true if the user-name exists in the hash-table and the password
	 * 			is correct, or the user-name doesn't appear in the DB, false otherwis
	 */
	public synchronized boolean isPasswordOK(String username, String password)
	{
		String pass = m_UserToPassword.get(username);
		if (pass == null || pass.equals(password))
			return true;
		return false;
	}
	
	/**
	 * synchronized the mappings. verifies that each UserProject in connectedUsers
	 * exists in the DB. every mapping in the DB that do not appear in connectedUsers
	 * will be deleted
	 * 
	 * @param connectedUsers - An array of users to verify the DB from
	 * @param serverID		 - The server ID that the array is from.
	 * @param compServers	 - The compilation server's hash-table
	 * 
	 */
	public synchronized void syncMappings(
			ArrayList<UserProject> connectedUsers,
			Long serverID,
			ServersHashTable compServers)
	{
		Set<Entry<UserProject, Long>> set = m_MYA_UserProjToServerID.entrySet();
		LinkedList<MYA_Authentication> list = new LinkedList<MYA_Authentication>();
		for (Entry<UserProject, Long> entry : set)
		{
			if (connectedUsers.contains(entry.getKey()))
			{
				//should not be deleted
				continue;
			}
			//else -	the connectedUsers list doesn't contain this mapping and
			//			it should be deleted
			compServers.unRegisterClient(serverID);
			list.add(new MYA_Authentication(
					entry.getKey().m_sUserName,
					m_UserToPassword.get(entry.getKey()),
					entry.getKey().m_sProjectName));
		}
		for (MYA_Authentication auth : list)
		{
			this.remove(auth);
		}
	}
	
	/**
	 * deletes a specific mapping.
	 * 
	 * @param userProj		 - a user project.
	 * @param serverID		 - a server ID.
	 * @param compServers	 - The compilation server's hash-table
	 * 
	 */
	public synchronized void deleteMapping(
			UserProject userProj,
			Long serverID,
			ServersHashTable compServers)
	{
		String password = m_UserToPassword.get(userProj.m_sUserName);
		if (password == null)
			return;
		compServers.unRegisterClient(serverID);
		this.remove(new MYA_Authentication(
				userProj.m_sUserName,
				password,
				userProj.m_sProjectName));
	}
	
	/**
	 * adds a user and a password to the hash-table
	 * 
	 * @param username - user name
	 * @param password - password
	 */
	public synchronized void addUserToUsersList(String username, String password)
	{
		m_UserToPassword.put(username, password);
	}
	
	/**
	 * adds a mapping to the hash table
	 * 
	 * @param authentication - a MYA_Authentication object
	 * @param serverID - a server
	 */
	public synchronized void add(MYA_Authentication authentication, Long serverID)
	{
		UserProject info = new UserProject(authentication.getUserName(),authentication.getProject());
		m_MYA_UserProjToServerID.put(info, serverID);
		m_UserToPassword.put(authentication.getUserName(), authentication.getPassword());
	}
	
	/**
	 * removes a mapping from the server
	 * 
	 * @param authentication - a MYA_Authentication object
	 * 
	 * @return The server that was mapped to the given argument
	 */
	public synchronized Long remove(MYA_Authentication authentication)
	{
		UserProject info = new UserProject(authentication.getUserName(),authentication.getProject());
		Long serverID = m_MYA_UserProjToServerID.remove(info);
		Set<Entry<UserProject, Long>> set = m_MYA_UserProjToServerID.entrySet();
		for (Entry<UserProject, Long> entry : set)
		{
			if (entry.getKey().m_sUserName.equals(authentication.getUserName()))
				return serverID;
		}
		m_UserToPassword.remove(authentication.getUserName());
		return serverID;
	}
	
	/**
	 * @return true <==> the mapping hash is empty
	 */
	public synchronized boolean isEmpty()
	{
		return (m_MYA_UserProjToServerID.size() == 0);
	}
	
	/**
	 * verifies the DB of the master server
	 * 
	 * @param serversHS - the server's hash-table
	 * 
	 */
	public synchronized void verifyDataBase(ServersHashTable serversHS)
	{
		Hashtable<Long, Integer> tmpArray = new Hashtable<Long, Integer>();
		Set<Entry<UserProject, Long>> set = m_MYA_UserProjToServerID.entrySet();
		for (Entry<UserProject, Long> entry : set)
		{
			long serverID = entry.getValue();
			Integer clients = tmpArray.remove(serverID);
			clients = (clients == null ? 0 : clients);
			tmpArray.put(serverID, clients + 1);
		}
		serversHS.updateData(tmpArray);
	}
	
	public 	MasterServer m_Parent;
	public static final String[] ColumnName = {"#","Username", "Password", "Project", "Mapped to Server"};
	public static final int ColumnCount = 5;

	
	
	@Override
	public void addTableModelListener(TableModelListener l) {
		// no implementation
		
	}

	private synchronized Class<?> getColumnClassSync(int columnIndex) {
		return getValueAt(0, columnIndex).getClass();
	}
	
	@Override
	public Class<?> getColumnClass(int columnIndex) {
		return getColumnClassSync(columnIndex);
	}

	@Override
	public int getColumnCount() {
		return ColumnCount;
	}

	@Override
	public String getColumnName(int columnIndex) {
		return ColumnName[columnIndex];
	}

	private synchronized int getRowCountSync()
	{
		return m_MYA_UserProjToServerID.size();
	}
	
	@Override
	public int getRowCount()
	{
		return getRowCountSync();
	}

	private synchronized Object getValueAtSync(int rowIndex, int columnIndex)
	{
		int i = 0;
		Entry<UserProject, Long> thisInfo = null;
		Set<Entry<UserProject, Long>> clientsSet = m_MYA_UserProjToServerID.entrySet();
		for (Entry<UserProject, Long> entry : clientsSet) 
		{
			if ((i++) == rowIndex)
			{
				thisInfo = entry;
				break;
			}
		}
		try {
			switch (columnIndex)
			{
				case 0: return i;
				case 1: return thisInfo != null ? thisInfo.getKey().m_sUserName : "n/a";
				case 2:
				{
					String pass = m_UserToPassword.get(thisInfo.getKey().m_sUserName);
					return pass != null ? pass : "n/a";
				}
				case 3: return thisInfo != null ? thisInfo.getKey().m_sProjectName  : "n/a";
				case 4:
				{
					return thisInfo.getValue().toString();
				}
			}
		}
		catch(Exception e)
		{
			return "n/a";
		}
		return null;
	}
	
	
	@Override
	public Object getValueAt(int rowIndex, int columnIndex)
	{
		return getValueAtSync(rowIndex,columnIndex);
	}

	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return false;
	}

	@Override
	public void removeTableModelListener(TableModelListener l) {
		// no implementation
	}

	@Override
	public void setValueAt(Object value, int rowIndex, int columnIndex) {
		// no implementation	
	}

}
